/*
This software is distributed under the Simplified BSD License:

Copyright (c) 2008, Chris Venter <chris.venter@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
    	this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
    	this list of conditions and the following disclaimer in the documentation 
    	and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE.
*/
#ifndef GENERIK_TESTING_H
#define GENERIK_TESTING_H

/** \file testing.h
This file provides a small unit-testing facility.

This unit-testing facility uses a test-specific xml file, along with this
header file, to generate source code which can be compiled into a unit test.

The test-specific code is placed in the xml file. The python script is run
on this xml file, which produces C++ output. Done this way, unit tests are
easy to construct and change, and easy to use.

A sample xml file is included, please see "testing.xml"
The python script resides in the "testing" file.

\note The Python script has been developed using 
Python 2.5.2 and as such may not work correctly under Python 3.0 and later
unless modified.
*/

#include <unistd.h>
#include <signal.h>
#include <generik/mingw.h>

#include <map>
#include <exception>
#include <iostream>

#include "exceptions.h"
#include "threads.h"
#include "tracing.h"

namespace generik
{

	/** An exception indicating that an expected exception has NOT been caught.
	It contains information about the expected exception.
	It is normally thrown after code in a GENERIK_CHECK_THROW() macro succesfully
	completed.
	It is intended to be caught by a basetester instance, and should not have to 
	be used by a user of this facility under normal conditions.
	*/
	struct failed_exception
	: std::exception
	{
		const char* msg;
		failed_exception(const char* m) : msg(m) {}
		const char* what() const throw()
		{
			return msg;
		}
	};

	/** The base test interface class.
	It declares methods to run and evaluate tests.
	*/
	struct testbase
	{
		int _errors;
	
		testbase()
		: _errors(0)
		{}
		virtual ~testbase() {}

		virtual void run() = 0;
	
		virtual int testcount() = 0;
		virtual int testsok() { return testcount() - _errors; }
	};

	/** The main tester class.
	This class runs all its tests and listens for any std::exceptions thrown.
	It is used by the testgroup class to encapsulate a specific test environement.

	It can register multiple test functions, and executes them in a 
	loop, accumulating errors along the way and listening for specific
	exceptions.
	*/
	struct tester
	: testbase
	{
		typedef void(*testfunction)();
		typedef std::map<std::string, testfunction> testfunctionmap;
		testfunctionmap _functions;

		void test(testfunction function)
		{
			(*function)();
		}
		virtual bool check(testfunction function)
		{
			GENERIK_TRACER;
			try 
			{
				test(function);
				GENERIK_TRACE("[PASS] testfunction passed");
				return true;
			}
			catch (failed_assertion& e)
			{
				GENERIK_ERROR("[NOTE] testfunction failed to pass assertion: "<<e.what());
			}
			catch (failed_exception& e)
			{
				GENERIK_ERROR("[NOTE] testfunction failed to throw exception: "<<e.what());
			}
			catch (std::exception& e)
			{
				GENERIK_ERROR("[NOTE] caught a std::exception ["<<e.what()<<"]");
			}
			catch (...)
			{
				throw;
			}
			++_errors;
			return false;
		}
		void run()
		{
			testfunctionmap::iterator i;
			for (i = _functions.begin(); i != _functions.end(); ++i)
			{
				bool result = check(i->second);
				if (!result)
				{
					break;
				}
			}
		}
		int testcount()
		{
			return _functions.size();
		}
	};

	struct timeout
	{
		size_t _seconds;
		timeout(size_t seconds_in)
		: _seconds(seconds_in)
		{}
		size_t seconds() { return _seconds; }
	};
	
	struct testtimer
	: thread
	{
		size_t _seconds;
		testtimer(size_t seconds)
		: _seconds(seconds)
		{
			GENERIK_TRACER;
		}
		
		static bool _woken;
		
		static void handle_wakeup(int sig)
		{
			_woken = true;
		}
		
		void* run()
		{
			GENERIK_TRACER;
			detach();
			_woken = false;
			signal(SIGINT, handle_wakeup);
			sleep(_seconds);
			if (!_woken)
			{
			GENERIK_TRACE("testtimer is timing out after "<<_seconds<<" seconds");
			throw timeout(_seconds);
		}
			GENERIK_TRACE("testtimer has been woken by the process");
			return 0;
		}
	};
	bool testtimer::_woken;
	
	struct testoptions
	{
		int _timeout;
		std::string _logfilename;
		
		void defaults()
		{
			_timeout = 1;
			_logfilename = "";
		}
		
		testoptions(int& argc, char**& argv)
		{
			GENERIK_TRACER;
			
			defaults();
			if (!parse(argc, argv))
			{
				GENERIK_TRACE("exiting...");
				exit(-1);
			}
		}
		
		void display_usage(const std::string& progname_in)
		{
			std::cerr<<"Usage: "<<progname_in<<" [-n] [-t <timeout>] [-f <logfile>]"<<std::endl;
			std::cerr<<"Where timeout: number of seconds to let test run"<<std::endl;
			std::cerr<<"      logfilename: name of logfile to put output in"<<std::endl;
			std::cerr<<"The default timeout is 1 second"<<std::endl;
			std::cerr<<"The default logfile is empty; instead, STDERR is used"<<std::endl;
		}
		
		bool parse(int& argc, char**& argv)
		{
			GENERIK_TRACER;
			
			GENERIK_TRACE_VALUE(argc);
			
			const char* optstring = "t:f:?";
			GENERIK_TRACE_VALUE(optstring);
			
			int opt = getopt(argc, argv, optstring);
			while( opt != -1 )
			{
				switch( opt )
				{
					case 't':
						_timeout = atoi(optarg);
						break;
					case 'f':
						_logfilename = optarg;
						break;
					case 'h':
					case '?':
						display_usage(argv[0]);
						return false;
						break;
					default:
						GENERIK_TRACE("default clause struck: this should not happen");
						break;
				}
				//
				opt = getopt(argc, argv, optstring);
			}
			
			if (optind != argc)
			{
				GENERIK_ERROR("Wrong invocation");
				display_usage(argv[0]);
				return false;
			}
			
			return true;
		}
	};

	/** The testgroup designates a number of testing situations in which to evaluate
	the integrity of a piece of code. It groups these situations into testers, each
	of which has multiple tests over which to stress the situation at hand.
	*/
	struct testgroup
	: testbase
	{
		testtimer _timer;
		
		std::map<std::string, testbase*> _testers;
		
		std::string _name;
		
		testgroup(const std::string& name, size_t seconds)
		: _timer(seconds)
		, _name(name)
		{
			GENERIK_TRACER;
		}
	
		void test(testbase* t)
		{
			t->run();
		}
		virtual void check(testbase* t)
		{
			GENERIK_TRACER;
			try
			{
				test(t);
			}
			catch(...)
			{
				GENERIK_ERROR("[ERROR] Unhandled exception caught: Execution aborted. See the error log for details");
				exit(-1);
			}
		}
		void run()
		{
			for (std::map<std::string, testbase*>::iterator i = _testers.begin(); i != _testers.end(); ++i)
			{
				check(i->second);
				if (i->second->_errors > 0)
				{
					++_errors;
					break;
				}
			}
		}
		int testcount()
		{
			return _testers.size();
		}
		int exec()
		{
			GENERIK_TRACER;
			try
			{
				_timer.execute();
			run();
			}
			catch(timeout& t)
			{
				GENERIK_ERROR("[ERROR] timeout triggered - the test function(s) took longer than "<<t.seconds()<<" seconds to complete");
				++_errors;
			}
			return _errors;
		}
	};
	
}

#define GENERIK_TESTER(NAME) struct NAME : generik::tester
#define GENERIK_TESTGROUP(NAME) struct NAME : generik::testgroup

#define GENERIK_ADDTEST(NAME) _functions[#NAME] = &NAME;
#define GENERIK_ADDTESTER(NAME) _testers[#NAME] = new NAME;

#define GENERIK_CHECK(VALUE) \
try \
{ \
	VALUE; \
} \
catch(...) \
{ \
	GENERIK_ERROR("[FAIL] unexpected exception from [" #VALUE "] in "<<__PRETTY_FUNC__<<GENERIK_FILE_LINE); \
	throw; \
}

#define GENERIK_CHECK_THROW(VALUE, EXCEPTION) \
try \
{ \
	VALUE; \
	GENERIK_ERROR("[FAIL] expected exception missing from [" #VALUE "] of type [" #EXCEPTION "] in "<<_PRETTY_FUNC__<<GENERIK_FILE_LINE); \
	throw generik::failed_exception("["#VALUE"] should throw ["#EXCEPTION"]"); \
} \
catch(EXCEPTION&) \
{ \
	GENERIK_TRACE("[PASS] caught expected exception of type [" #EXCEPTION "] in "<<__PRETTY_FUNC__<<GENERIK_FILE_LINE); \
}


#endif


